/**
 * Copyright 2014 the original author or authors. All rights reserved.
 */
package com.visionet.security.utils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.visionet.jumper.utils.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.visionet.jumper.core.exception.runtime.FrameworkException;
import com.visionet.security.base.constants.ContentType;

/**
 * 
 *
 * @author suxiaojing
 * @date
 * @since 0.0.1
 */
public class WebUtils {
	public final static Logger logger = LoggerFactory.getLogger(WebUtils.class);
	/**
	 * Standard Servlet 2.3+ spec request attributes for include URI and paths.
	 * <p>
	 * If included via a RequestDispatcher, the current resource will see the originating request.
	 * Its own URI and paths are exposed as request attributes.
	 */
	public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";

	public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";

	public static final String INCLUDE_SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path";

	public static final String INCLUDE_PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info";

	public static final String INCLUDE_QUERY_STRING_ATTRIBUTE = "javax.servlet.include.query_string";

	/**
	 * Standard Servlet 2.4+ spec request attributes for forward URI and paths.
	 * <p>
	 * If forwarded to via a RequestDispatcher, the current resource will see its own URI and paths.
	 * The originating URI and paths are exposed as request attributes.
	 */
	public static final String FORWARD_REQUEST_URI_ATTRIBUTE = "javax.servlet.forward.request_uri";

	public static final String FORWARD_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.forward.context_path";

	public static final String FORWARD_SERVLET_PATH_ATTRIBUTE = "javax.servlet.forward.servlet_path";

	public static final String FORWARD_PATH_INFO_ATTRIBUTE = "javax.servlet.forward.path_info";

	public static final String FORWARD_QUERY_STRING_ATTRIBUTE = "javax.servlet.forward.query_string";

	/**
	 * Default character encoding to use when <code>request.getCharacterEncoding</code> returns
	 * <code>null</code>, according to the Servlet spec.
	 * 
	 * @see javax.servlet.ServletRequest#getCharacterEncoding
	 */
	public static final String DEFAULT_CHARACTER_ENCODING = "UTF-8";

	/**
	 * A convenience method that merely casts the incoming <code>ServletRequest</code> to an
	 * <code>HttpServletRequest</code>:
	 * <p/>
	 * <code>return (HttpServletRequest)request;</code>
	 * <p/>
	 * Logic could be changed in the future for logging or throwing an meaningful exception in non
	 * HTTP request environments (e.g. Portlet API).
	 * 
	 * @param request
	 *            the incoming ServletRequest
	 * @return the <code>request</code> argument casted to an <code>HttpServletRequest</code>.
	 */
	public static HttpServletRequest toHttp(ServletRequest request) {
		return (HttpServletRequest) request;
	}

	/**
	 * A convenience method that merely casts the incoming <code>ServletResponse</code> to an
	 * <code>HttpServletResponse</code>:
	 * <p/>
	 * <code>return (HttpServletResponse)response;</code>
	 * <p/>
	 * Logic could be changed in the future for logging or throwing an meaningful exception in non
	 * HTTP request environments (e.g. Portlet API).
	 * 
	 * @param response
	 *            the outgoing ServletResponse
	 * @return the <code>response</code> argument casted to an <code>HttpServletResponse</code>.
	 */
	public static HttpServletResponse toHttp(ServletResponse response) {
		return (HttpServletResponse) response;
	}

	/**
	 * Return full request URI with query string
	 * 
	 * @param request
	 * @return the full request URI
	 */
	public static String getFullRequestURI(HttpServletRequest request) {
		String contextPath = getContextPath(request);
		String requestUri = getRequestUri(request);
		if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
			String path = requestUri.substring(contextPath.length());
			if (StringUtils.isNotEmpty(path)) {
				String queryString = getQueryString(request);
				if (StringUtils.isNotEmpty(queryString)) {
					return path + "?" + queryString;
				}
				return path;
			} else {
				// Normal case: URI contains context path.
				return "/";
			}
		} else {
			// Special case: rather unusual.
			return requestUri;
		}
	}

	/**
	 * Decode the given source string with a URLDecoder. The encoding will be taken from the
	 * request, falling back to the default "ISO-8859-1".
	 * <p>
	 * The default implementation uses <code>URLDecoder.decode(input, enc)</code>.
	 * 
	 * @param request
	 *            current HTTP request
	 * @param source
	 *            the String to decode
	 * @return the decoded String
	 * @see #DEFAULT_CHARACTER_ENCODING
	 * @see javax.servlet.ServletRequest#getCharacterEncoding
	 * @see java.net.URLDecoder#decode(String, String)
	 * @see java.net.URLDecoder#decode(String)
	 */
	public static String decodeRequestString(HttpServletRequest request, String source) {
		String enc = determineEncoding(request);
		try {
			return URLDecoder.decode(source, enc);
		} catch (UnsupportedEncodingException ex) {
			logger.warn(
					"Could not decode request string [{}] with encoding [{}]: falling back to platform default encoding; exception message: [{}]",
					source, enc, ex.getMessage());
			try {
				return URLDecoder.decode(source, DEFAULT_CHARACTER_ENCODING);
			} catch (UnsupportedEncodingException e) {
				logger.error("Could not decode reqeust string [{}] with default encoding '[{}]'",
						source, DEFAULT_CHARACTER_ENCODING);
				throw new FrameworkException(e.getMessage(), e);
			}
		}
	}

	/**
	 * Determine the encoding for the given request. Can be overridden in subclasses.
	 * <p>
	 * The default implementation checks the request's {@link ServletRequest#getCharacterEncoding()
	 * character encoding}, and if that <code>null</code>, falls back to the
	 * {@link #DEFAULT_CHARACTER_ENCODING}.
	 * 
	 * @param request
	 *            current HTTP request
	 * @return the encoding for the request (never <code>null</code>)
	 * @see javax.servlet.ServletRequest#getCharacterEncoding()
	 */
	protected static String determineEncoding(HttpServletRequest request) {
		String enc = request.getCharacterEncoding();
		if (enc == null) {
			enc = DEFAULT_CHARACTER_ENCODING;
		}
		return enc;
	}

	/**
	 * Return the path within the web application for the given request. Detects include request URL
	 * if called within a RequestDispatcher include.
	 * <p/>
	 * For example, for a request to URL
	 * <p/>
	 * <code>http://www.somehost.com/myapp/my/url.jsp</code>,
	 * <p/>
	 * for an application deployed to <code>/mayapp</code> (the application's context path), this
	 * method would return
	 * <p/>
	 * <code>/my/url.jsp</code>.
	 * 
	 * @param request
	 *            current HTTP request
	 * @return the path within the web application
	 */
	public static String getPathWithinApplication(HttpServletRequest request) {
		String contextPath = getContextPath(request);
		String requestUri = getRequestUri(request);
		if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
			// Normal case: URI contains context path.
			String path = requestUri.substring(contextPath.length());
			return (StringUtils.isNotEmpty(path) ? path : "/");
		} else {
			// Special case: rather unusual.
			return requestUri;
		}
	}

	/**
	 * Return the context path for the given request, detecting an include request URL if called
	 * within a RequestDispatcher include.
	 * <p>
	 * As the value returned by <code>request.getContextPath()</code> is <i>not</i> decoded by the
	 * servlet container, this method will decode it.
	 * 
	 * @param request
	 *            current HTTP request
	 * @return the context path
	 */
	public static String getContextPath(HttpServletRequest request) {
		String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE);
		if (contextPath == null) {
			contextPath = request.getContextPath();
		}
		if ("/".equals(contextPath)) {
			// Invalid case, but happens for includes on Jetty: silently adapt
			// it.
			contextPath = "";
		}
		return decodeRequestString(request, contextPath);
	}

	/**
	 * Return the request URI for the given request, detecting an include request URL if called
	 * within a RequestDispatcher include.
	 * <p>
	 * As the value returned by <code>request.getRequestURI()</code> is <i>not</i> decoded by the
	 * servlet container, this method will decode it.
	 * <p>
	 * The URI that the web container resolves <i>should</i> be correct, but some containers like
	 * JBoss/Jetty incorrectly include ";" strings like ";jsessionid" in the URI. This method cuts
	 * off such incorrect appendices.
	 * 
	 * @param request
	 *            current HTTP request
	 * @return the request URI
	 */
	public static String getRequestUri(HttpServletRequest request) {
		String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
		if (uri == null) {
			uri = request.getRequestURI();
		}
		return decodeAndCleanUriString(request, uri);
	}

	/**
	 * Return query string for the given request, detecting an include query string if called within
	 * a RequestDispatcher include.
	 * <p>
	 * As the value returned by <code>request.getQueryString()</code> is <i>not</i> decoded by the
	 * servlet container, this method will decode it.
	 * <p>
	 * 
	 * @param request
	 *            current HTTP request
	 * @return the query string
	 */
	public static String getQueryString(HttpServletRequest request) {
		String qyeryString = (String) request.getAttribute(INCLUDE_QUERY_STRING_ATTRIBUTE);
		if (qyeryString == null) {
			qyeryString = request.getQueryString();
		}
		if (StringUtils.isEmpty(qyeryString)) {
			return null;
		}
		return decodeRequestString(request, qyeryString);
	}

	/**
	 * Decode the supplied URI string and strips any extraneous portion after a ';'.
	 * 
	 * @param request
	 *            the incoming HttpServletRequest
	 * @param uri
	 *            the application's URI string
	 * @return the supplied URI string stripped of any extraneous portion after a ';'.
	 */
	private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
		uri = decodeRequestString(request, uri);
		int semicolonIndex = uri.indexOf(';');
		return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
	}

	/**
	 * Transform object to json and write to response
	 * 
	 * @param value
	 *            The object to be transform json
	 * @param response
	 *            The outgoing response
	 */
	public static void writeJsonResponse(Object value, HttpServletResponse response) {
		try {
			response.setContentType(ContentType.JSON_UTF8);
			JsonUtils.toOutputStream(response.getOutputStream(), value);
		} catch (Exception e) {
			logger.error("Write json to response due to error");
		}
	}

}
